var TileViewer = new function ()
{
    var _tileCreator = null;
    var _titleUpdator = null;
    var _tileResetter = null;
    var _backgroundImage = null;
    var _configHandler = null;
    var _configOptions = null;
    var _tileWidth = null;
    var _tileHeight = null;
    var _viewportContainer = null;
    var _viewportSize = { x: 0, y: 0 };
    var _tiles = [];
    var _tilesBeingRemoved = [];
    var _tilePool = new Queue();
    var _tileInfo = [];
    var _posX = 0;
    var _posY = 0;
    var _sizeX = 0;
    var _sizeY = 0;
    var _scrollX = 0;
    var _scrollY = 0;
    var _targetScrollX = 0;
    var _targetScrollY = 0;
    var _tileDomWaitingArea = $("<div></div>");
    var _animationDurationInMs = 0;
    var _didConfig = false;

    var self = this;
    self.setTileCreator = function (tileCreator)
    { _tileCreator = tileCreator; }

    self.setTileUpdator = function (tileUpdator)
    { _tileUpdator = tileUpdator; }

    self.setTileResetter = function (tileResetter)
    { _tileResetter = tileResetter; }

    self.setBackgroundImage = function (url)
    {
        _backgroundImage = url;
        //todo update background image
    }

    self.setViewportContainer = function (viewportContainer)
    {
        if (_viewportContainer)
        { _viewportContainer.empty(); }
        _viewportContainer = viewportContainer;
        var temp = [];
        for (var id in _tileInfo)
        { temp[id] = _tileInfo[id]; }
        for (var id in temp)
        {
            var pos = invkey(id);
            self.updateTile(pos.x, pos.y, temp[id]);
        }
        _viewportSize.x = _viewportContainer.width();
        _viewportSize.y = _viewportContainer.height();
    }

    self.getViewportSize = function ()
    {
        var size = { x: _viewportSize.x, y: _viewportSize.y };
        return size;
    }

    self.setCenterTile = function (x, y)
    {
        if (x != _targetScrollX || y != _targetScrollY)
        {
            _targetScrollX = x;
            _targetScrollY = y;
            refreshAllTiles();
        }
    }

    function refreshAllTiles()
    {
        _scrollX = ((_viewportSize.x / _tileWidth) / 2) - _targetScrollX;
        _scrollY = ((_viewportSize.y / _tileHeight) / 2) - _targetScrollY;

        var temp = [];
        for (var id in _tiles)
        { temp.push(_tiles[id]); }
        for (var i = 0; i < temp.length; i++)
        { hideTile(temp[i]); }
        for (var key in _tileInfo)
        {
            var xy = unkey(key);
            self.updateTile(xy.x, xy.y, _tileInfo[key]);
        }
    }

    self.setAnimationDuration = function (newDurationInMs)
    {
        _animationDurationInMs = newDurationInMs;
    }

    function key(x, y)
    { return x + "." + y; }

    function unkey(key)
    {
        var parts = key.split(".");
        var result = { x: Number(parts[0]), y: Number(parts[1]) };
        return result;
    }

    function invkey(id)
    {
        var temp = id.split('.');
        return { x: parseInt(temp[0]), y: parseInt(temp[1]) };
    }

    self.updateTile = function (x, y, info)
    {
        if (!_didConfig)
        { self.config({}); }
        var id = key(x, y);
        _tileInfo[id] = info;

        if (!isVisible(x, y))
        { return; }

        var tile = null;
        if (_tilePool.getLength() == 0)
        {
            if (!_tileCreator)
            { throw "No Tile Creator Defined" }
            tile = _tileCreator();
            tile.container.css("position", "absolute");
        }
        else
        { tile = _tilePool.dequeue(); }
        tile.key = id;
        tile.container.css({ "width": _tileWidth, "height": _tileHeight });

        var whendone = function ()
        {
            if (_tileInfo[id] == info)
            {
                updatedTileReady(x, y, info, tile);
            }
        }

        if (!_tileUpdator)
        { throw "No Tile Updator Defined"; }
        _tileUpdator(tile, info, whendone);
    }

    self.removeTile = function (x, y)
    {
        var id = key(x, y);
        delete _tileInfo[id];
        var oldTile = _tiles[id];
        if (oldTile)
        { hideTile(oldTile); }
    }

    function isVisible(x, y)
    {
        var rxy = getRealXY(x, y);
        if (rxy.x < (0 - _tileWidth) || rxy.y < (0 - _tileHeight))
        { return false; }
        if (rxy.x > _viewportSize.x || rxy.y > _viewportSize.y)
        { return false; }
        return true;
    }

    function updatedTileReady(x, y, info, tile)
    {
        var id = key(x, y);
        var rxy = getRealXY(x, y);
        var oldTile = _tiles[id];
        if (oldTile)
        { hideTile(oldTile); }
        var domB = tile.container;
        domB.stop(true, true);
        domB.css({ opacity: 0, "z-index": 2 });
        domB.css({ left: rxy.x, top: rxy.y, width: _tileWidth, height: _tileHeight });
        _viewportContainer.append(domB);
        domB.fadeTo(_animationDurationInMs, 1);

        _tiles[id] = tile;
    }

    function getRealXY(x, y)
    {
        var rxy =
		{
		    x: (x + _scrollX) * _tileWidth,
		    y: (y + _scrollY) * _tileHeight
		}
        return rxy;
    }

    self.reset = function ()
    {
        for (var key in _tileInfo)
        {
            _tileInfo[key] = null;
            hideTile(_tiles[key]);
        }
        _tileInfo[key] = [];
        _posX = 0;
        _posY = 0;
        _sizeX = 0;
        _sizeY = 0;
    }

    function hideTile(tile)
    {
        if (_tiles[tile.key] == tile)
        {
            delete _tiles[tile.key];
            _tilesBeingRemoved[tile.key] = tile;
        }
        var dom = tile.container;
        dom.stop(true, true);
        dom.css({ opacity: 1, "z-index": 1 });
        //dom.fadeTo(_animationDurationInMs, 0, removeTileCB(tile));
        setTimeout(removeTileCB(tile), _animationDurationInMs);
    }

    function removeTileCB(tile)
    { return function () { removeTile(tile); } }
    function removeTile(tile)
    {
        if (_tiles[tile.key] == tile)
        {
            delete _tiles[tile.key];
        }
        delete _tilesBeingRemoved[tile.key];
        var dom = tile.container;
        dom.stop(true, true);
        _tileDomWaitingArea.append(dom);
        if (_tileResetter)
        {_tileResetter(tile, function(){removeTileDone(tile);});}
        else
        {removeTileDone(tile);}
    }
    function removeTileDone(tile)
    {
        _tilePool.enqueue(tile);
    }

    var InternalConfigInterface = new function ()
    {
        var s2 = this;
        s2.setTileWidth = function (widthInPx)
        { _tileWidth = widthInPx; }

        s2.setTileHeight = function (heightInPx)
        { _tileHeight = heightInPx; }
    } ();

    function doConfig()
    {
        if (_configOptions)
        {
            if (!_configOptions.tileWidth)
            { _configOptions.tileWidth = 32; }
            if (!_configOptions.tileHeight)
            { _configOptions.tileHeight = 32; }

            InternalConfigInterface.setTileWidth(_configOptions.tileWidth);
            InternalConfigInterface.setTileHeight(_configOptions.tileHeight);

            if (_configHandler)
            { _configHandler(_configOptions); }
            _didConfig = true;
            refreshAllTiles();
        }
    }

    self.setConfigHandler = function (configHandler)
    {
        _configHandler = configHandler;
        doConfig();
    }


    self.config = function (config)
    {
        _configOptions = config;
        doConfig();
    }
} ();